import Vue from 'vue'
import SparkMD5 from 'spark-md5'
import {initChunkUpload, mergeChunk, uploadFileChunkBuffer} from "@/api/uploadFile";
import {publishMessage} from "@/utils/publishUtil";
import store from '@/store'

const UPLOAD_STATUS = {
  //  上传中
  UPLOADING: 'UPLOADING', //  暂停
  TIMEOUT: 'TIMEOUT', //  已完成
  COMPLETED: 'COMPLETED'
};

const FILE_UPLOAD_STATUS = {
  //  等待
  AWAIT: 'AWAIT', //  上传中
  UPLOADING: 'UPLOADING', //  暂停
  TIMEOUT: 'TIMEOUT', //  已完成
  COMPLETED: 'COMPLETED'
};

class UploadFileObj {

  //  最切片大小
  _minSize = 1024 * 1024 * 1;
  //  当前上传文件索引
  _currentBaseIndex = 0;
  //  上传文件列表
  _fileChunksList = [];
  //  回调函数，用于通知前端组件
  _bakFun = null
  //  当前http请求的source
  _sourceToken = null
  //  上传开关
  _uploadSwitch = true;
  //  当前上传文件的RecordId
  _currentRecordId = '';

  _currentStatus = '';


  static getInstance() {
    //判断是否之前创建过实例，有的话就return 没有就创建一个
    if (!UploadFileObj.instance) UploadFileObj.instance = new UploadFileObj()
    return UploadFileObj.instance
  }

  setBackFun(bakFun) {
    this._bakFun = bakFun
  }

  /**
   *
   * @param minSize   最小切片大小
   * @param bakFun    回传数据的方法
   */
  constructor(minSize = this._minSize, bakFun = null) {
    this._minSize = _.defaultTo(minSize, this._minSize);
  }

  //  上传
  async uploadFile(submitUrl, params) {
    let fileList = await this.getFile()
    fileList = _.map(fileList, item => {
      item.submitUrl = _.cloneDeep(submitUrl)
      item.params = _.cloneDeep(params)
      return item
    })
    let bufferList = await this.getFileListChunks(fileList);
    this._fileChunksList = [...this._fileChunksList, ...bufferList]

    publishMessage('showUpload', UploadFileObj.instance)
    let owner = this

    setTimeout(() => {
      owner.backhaulData()
    }, 100)

    this._uploadSwitch = true
    if (this._currentStatus != UPLOAD_STATUS.UPLOADING) {
      this.startTheUpload()
    }
  };

  //  回传数据
  backhaulData() {
    let {_bakFun} = this
    if (_bakFun != null && typeof _bakFun == 'function') {
      _bakFun(_.cloneDeep(this._fileChunksList), this._currentStatus)
    }
  }

  //  开始上传
  async startTheUpload() {
    this._uploadSwitch = true;
    let {_fileChunksList} = this;
    this._currentStatus = UPLOAD_STATUS.UPLOADING;
    //  遍历文件列表
    for (; this._currentBaseIndex < this._fileChunksList.length && this._uploadSwitch;) {
      if (this._fileChunksList[this._currentBaseIndex].status == FILE_UPLOAD_STATUS.COMPLETED || this._fileChunksList[this._currentBaseIndex].status == FILE_UPLOAD_STATUS.TIMEOUT) {
        if (this._uploadSwitch) {
          this._currentBaseIndex += 1;
        }
        continue;
      }
      this._fileChunksList[this._currentBaseIndex].status = FILE_UPLOAD_STATUS.UPLOADING;
      let currentFileChunkItem = this._fileChunksList[this._currentBaseIndex];
      this.backhaulData();
      //  遍历当前文件的切片列表
      for (; currentFileChunkItem.currentIndex < currentFileChunkItem.total && this._uploadSwitch;) {
        if (currentFileChunkItem.currentIndex == 0) {
          await this.firstRequest(currentFileChunkItem)
        }
        await this.uploadChunk(currentFileChunkItem);
        if (currentFileChunkItem.currentIndex == currentFileChunkItem.total - 1) {
          await this.lastRequest(currentFileChunkItem)
        }
        if (this._uploadSwitch) {
          this._fileChunksList[this._currentBaseIndex].currentIndex += 1;
        }
      }
      this._fileChunksList[this._currentBaseIndex].status = FILE_UPLOAD_STATUS.COMPLETED;
      this.backhaulData();
      if (this._uploadSwitch) {
        this._currentBaseIndex += 1;
      }
    }

    let bufferList = _.filter(this._fileChunksList, item => item.status == FILE_UPLOAD_STATUS.COMPLETED);

    this._currentStatus = bufferList.length == this._fileChunksList.length ? UPLOAD_STATUS.COMPLETED : UPLOAD_STATUS.TIMEOUT
    this.backhaulData()
  }

  //  先行请求
  firstRequest(currentFileChunkItem) {
    let owner = this;
    return new Promise(async (resolve, reject) => {
      let {chunkList, total, currentIndex, fileHash, name, size, type} = currentFileChunkItem;
      let res = await initChunkUpload({fileMd5Value: fileHash, name, size, total, type});
      owner._currentRecordId = res.data.id;
      resolve()
    })
  }

  //  合并请求
  lastRequest(currentFileChunkItem) {
    return new Promise(async (resolve, reject) => {
      let {params, submitUrl} = currentFileChunkItem;
      let res = await mergeChunk(this._currentRecordId)
      let {data} = res
      await store.dispatch(submitUrl, _.assign(params, data))
      resolve()
    })
  }

  //  上传切片
  uploadChunk(currentFileChunkItem) {
    return new Promise(async (resolve, reject) => {
      let {chunkList, fileHash, name, size, type, currentIndex} = currentFileChunkItem;
      const formData = new FormData();
      formData.append('data', chunkList[currentIndex]);
      formData.append('fileHash', fileHash);
      formData.append('name', name);
      await uploadFileChunkBuffer(formData, this._currentRecordId, currentIndex, {
        currentBaseIndex: this._currentBaseIndex, currentIndex
      }, this.modifyTheProgress, this.setSourceToken, this)
      resolve()
    })
  }

  //  设置请求源，用于取消请求
  setSourceToken(sourceToken, owner) {
    owner._sourceToken = sourceToken
  }


  //  暂停请求
  pauseUpload() {
    this._sourceToken.cancel('暂停上传')
    if (this._fileChunksList[this._currentBaseIndex]) {
      this._fileChunksList[this._currentBaseIndex].loadList[this._fileChunksList[this._currentBaseIndex].currentIndex] = 0
      this._fileChunksList[this._currentBaseIndex].status = UPLOAD_STATUS.TIMEOUT;
    }
    this._currentBaseIndex = 0;
    this.startTheUpload();
    this.backhaulData()
    // this._uploadSwitch = false
  }

  pauseAll() {
    this._sourceToken.cancel('暂停上传')
    this._fileChunksList[this._currentBaseIndex].loadList[this._fileChunksList[this._currentBaseIndex].currentIndex] = 0
    this._fileChunksList = _.map(this._fileChunksList, item => {
      item.uploadedSize = _.sum(item.loadList)
      item.status = item.uploadedSize >= item.size ? FILE_UPLOAD_STATUS.COMPLETED : FILE_UPLOAD_STATUS.TIMEOUT
      return item
    })
    this._currentBaseIndex = 0;
    this._currentStatus = UPLOAD_STATUS.TIMEOUT
    this.backhaulData()
  }

  continueAllUploading() {
    this._fileChunksList = _.map(this._fileChunksList, item => {
      item.status = item.uploadedSize == item.size ? FILE_UPLOAD_STATUS.COMPLETED : FILE_UPLOAD_STATUS.AWAIT
      return item
    })
    this._currentBaseIndex = 0;
    this._currentStatus = UPLOAD_STATUS.UPLOADING
    this.startTheUpload();
    this.backhaulData()
  }

  continueUploading(index) {
    this._currentBaseIndex = 0;
    this._fileChunksList[index].status = FILE_UPLOAD_STATUS.AWAIT
    if (this._currentStatus == UPLOAD_STATUS.TIMEOUT) {
      this.startTheUpload()
    }
  }

  //  取消上传
  cancelUpload(currentIndex) {

  }

  //  取消所有上传
  cancelAllUpload() {
    this.pauseUpload()
    this._fileChunksList = []
    this._currentBaseIndex = 0
    this.backhaulData()
  }

  //  修改进度
  modifyTheProgress(e, backData, data, owner) {
    let {currentBaseIndex, currentIndex} = backData

    owner._fileChunksList[currentBaseIndex].loadList[currentIndex] = e.loaded
    let sumSize = _.sum(owner._fileChunksList[currentBaseIndex].loadList)
    owner._fileChunksList[currentBaseIndex].uploadedSize = sumSize >=  owner._fileChunksList[currentBaseIndex].size ?  owner._fileChunksList[currentBaseIndex].size : sumSize

    let progress = _.round(owner._fileChunksList[currentBaseIndex].uploadedSize / owner._fileChunksList[currentBaseIndex].size * 100, 2)
    owner._fileChunksList[currentBaseIndex].progress = progress > 100 ? 100 : progress
    owner.backhaulData()
  }

  //  获取文件
  getFile() {
    return new Promise((resolve, reject) => {
      let uploadDom = this.createdElement('input');
      uploadDom.type = 'file';
      uploadDom.multiple = "multiple"
      uploadDom.click()
      uploadDom.onchange = function (e) {
        let fileList = e.target.files;
        resolve(fileList)
      }
    })
  };


  //  构建dom
  createdElement(type, classList, childList) {
    let dom = document.createElement(type);
    if (Array.isArray(classList) && classList?.length) {
      _.forEach(classList, (className, index) => {
        dom.classList.add(className)
      })
    }
    if (Array.isArray(childList) && childList?.length) {
      _.forEach(childList, (child, index) => {
        dom.appendChild(child)
      })
    }
    return dom
  }

  //  获取文件切片数据
  getFileListChunks(fileList) {
    let owner = this
    return new Promise(async (resolve, reject) => {
      let res = []
      for (let i = 0; i < fileList.length; i++) {
        let file = fileList[i];
        let {name, size, type, params, submitUrl} = file
        let {chunkList, fileHash, fileReader} = await owner.getFileChunks(file, this._minSize);
        res.push({
          id: null,
          params,
          submitUrl,
          loadList: [],
          chunkList,
          fileHash,
          name,
          size,
          type,
          total: chunkList.length,
          currentIndex: 0,
          progress: 0,
          uploadedSize: 0,
          status: FILE_UPLOAD_STATUS.AWAIT
        })
      }
      resolve(res)
    })
  };

  //  文件切片方法
  getFileChunks(file, defualtChunkSize) {
    return new Promise((resolve, reject) => {
      let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
      let chunks = Math.ceil(file.size / defualtChunkSize);
      let currentChunk = 0;
      let spark = new SparkMD5.ArrayBuffer();
      let fileReader = new FileReader();
      let chunkList = []

      fileReader.onload = function (e) {
        const chunk = e.target.result;
        spark.append(chunk);
        currentChunk++;

        if (currentChunk < chunks) {
          loadNext();
        } else {
          let fileHash = spark.end();
          console.info('finished computed hash', fileHash);
          // 此处为重点，计算完成后，仍然通过postMessage通知主线程
          resolve({chunkList, fileHash, fileReader})
        }
      };

      fileReader.onerror = function () {
        console.warn('oops, something went wrong.');
      };

      function loadNext() {
        let start = currentChunk * defualtChunkSize
        let end = ((start + defualtChunkSize) >= file.size) ? file.size : start + defualtChunkSize;
        let chunk = blobSlice.call(file, start, end);
        chunkList.push(chunk)
        fileReader.readAsArrayBuffer(chunk);
      }

      loadNext();
    })
  }
}

export {UploadFileObj, FILE_UPLOAD_STATUS, UPLOAD_STATUS}
